Observer הוא פריט אחד מתוך רשימה מכובדת של Design Patterns. הוא מתפקד כסוג של מנהל אירועים – בקרה לכל חלק במערכת ועוד.
שלום לכל כרישי PHP Guide.
אני מניח שחלקכם שמעתם על Design Patterns או בקיצור DP, ולאלה שעוד לא שמעו – אספר בקצרה במה בכלל מדובר. :)
Design Patterns
Design Patterns הן פתרונות לבעיות נפוצות, אפשר לומר. כמו תוכניות מגירה למקרה שנתקל בבעיה נפוצה. לדוגמה, אם יורד גשם ואתם חייבים לצאת, אתם יכולים לחשוב על הרבה פתרונות – חלק טובים יותר, חלק פחות, אבל במקום שתחפשו פתרון ותבדקו כל דבר, למישהו כבר הייתה בעיה כזו לפניכם, והוא פתר אותה. איש לא פתר אותה טוב יותר אחריו. הוא המציא את המטרייה. ועד שאדם אחר לא יפתח משהו שימושי יותר ממטרייה, אין לנו סיבה לשבור את הראש ולחפש אחר פתרונות; אנחנו פשוט נכין מטרייה מראש, וזו תבנית ההתנהגות שלנו לגבי הבעיה הזו. (תבנית = Pattern)
גם בעולם התוכנה יש בעיות שונות. לדוגמה, Singleton בא למנוע יצירה של אובייקט יותר מפעם אחת (בטעות), ויש עוד הרבה תבניות שונות מסוגים שונים לצרכים שונים.
Observer Pattern
אחת התבניות השימושיות ביותר היא Observer, או בעברית – "המשקיף".
אם אנחנו רוצים לעשות פעולה מסוימת – לנטר, לעקוב, לדווח – ברגע שאירוע מסוים מתבצע במערכת, איך נעשה את זה? נשתמש בפונקציה ששולחת דוא"ל בכל פעם שמשתמש נרשם? נכתוב פונקציה שמכניסה שורה למסד הנתונים בכל פעם שמישהו מתחבר (לצורכי אבטחה)? ואם יש לנו עוד הרבה אירועים שונים במערכת שהיינו רוצים לנתר, מה נעשה אז? איך נעשה את זה חכם, כך שזה לא יפגע בגמישות של המערכת, ובעתיד, כשנרצה להוסיף לזה מידע שונה וכדו', לא נצטרך לערוך את קוד המערכת התקין שלנו (אחד מעקרונות התוכנה), אלא רק את אותה פעולה ספציפית שצריכה לקרות? איך נפריד אותה מהשאר?
Observer פשוט
ניקח תכונות בסיסיות של המשקיף (Observer) ונכניס אותם לתוך Interface, כך שנוכל לראות בבירור כל מודל שיצטרך משקיף ולחייב אותו להשתמש בפונקציות של המשקיף. ככה נמנע טעויות, ונוודא שהפונקציות של המשקיף קיימות בוודאות.
למען הנוכחות, אלו רשימת הקבצים שנצטרך:
IObserver.php – האינטרפייס שלנו (לא חובה, אבל תעשו טובה לעצמכם...)
Observer.php – משקיף אבסטרקטי, ממנו ניצור את המשקיפים השונים.
LoginObserver.php – משקיף ספציפי שיצרתי לצורך הדוגמה, משקיף שקשור להתחברות, כמובן.
ModelAbstract.php – ממנה כל המודלים יורשים, וכאן יושם האינטרפייס שממנו כל המודלים ירשו. בדרך כלל מודל אבסטרקט כזה קיים בכל מערכת MVC.
LoginModel.php – מחלקת ההתחברות עצמה, בשביל הדוגמא נעשה לוג של התחברות.
IObserver.php
interface IObserver {
public function attach(Observer $observer);
public function dettach(Observer $observer);
public function setstat($msg);
public function notify();
}
public function attach(Observer $observer);
public function dettach(Observer $observer);
public function setstat($msg);
public function notify();
}
אלו ארבעת פונקציות הבסיס. כמובן שכמו ב-Interface רושמים רק את החתימות של הפונקציה, ומרגע זה, כל מודל (login.php, לדוגמה) יעשה איפלמינטציה (יישום) של האינטרפייס הזה, כך שנוכל לזהות בקלות מודלים המשתמשים במשקיף וגם לדעת בוודאות שמישהו לא מחק בטעות איזושהי פונקציה שקשורה לזה.
הפונקציה attach תחבר משקיף למודל שלנו; הפונקציה dettach – נחשו לבד; מהפונקציה setstat יתקבל המידע למשקיף (יכול להיות טקסט, אבל עדיף מערך מידע); והפונקציה notify תשלח את הנתונים.
Observer.php
abstract class Observer {
protected $msg = "";
public function __construct() {
}
public function setstat($msg){
$this->msg = $msg;
}
abstract function run();
}
protected $msg = "";
public function __construct() {
}
public function setstat($msg){
$this->msg = $msg;
}
abstract function run();
}
זהו בסיס המשקיפים שממנו ניצור את כל המשקיפים השונים לכל הדברים שלנו וממנו נירש.
הפונקציה setstat מקבלת את הנתונים ושומרת; את הפונקציה run נצטרך לממש מחדש במשקיף הייחודי שניצור, וכאן אנחנו נחליט מה עושים עם המידע של אותו משקיף. בדוגמה שלנו יהיה משקיף שאחראי על שמירת לוגים של התחברויות, ולכן הוא יקבל מערכת מידע, ועם פונקציית run ישמור את זה בצורה שצריך במסד.
כל משקיף ומה שהוא עושה עם המידע שלו.
LoginObserver.php
class LoginObserver extends Observer {
public function run(){
//some db query or whatever with $this->msg data!
}
}
public function run(){
//some db query or whatever with $this->msg data!
}
}
כאן יצרנו את המשקיף הייחודי שיורש מהמשקיף הבסיסי שתיארנו ממש מעל זה.
כאן אנחנו גם מממשים את פונקציית run בצורה הייחודית של אותו משקיף. (ראו הערה ליד הפונקציה)
ModelAbstract.php
abstract class ModelAbstract implements IObserver{
private $observers = array();
public function attach(Observer $observer) {
$this->observers[] = $observer;
return $this;
}
public function dettach(Observer $observer) {
$newObservers = array();
foreach ($this->observers as $oldObserver) {
if ($oldObserver !== $observer) {
$newObservers[] = $oldObserver;
}
}
$this->observers = $newObservers;
return $this;
}
public function notify() {
foreach($this->observers as $observer) {
$observer->run();
}
}
public function setstat($msg){
foreach($this->observers as $observer) {
$observer->setstat($msg);
}
}
}
private $observers = array();
public function attach(Observer $observer) {
$this->observers[] = $observer;
return $this;
}
public function dettach(Observer $observer) {
$newObservers = array();
foreach ($this->observers as $oldObserver) {
if ($oldObserver !== $observer) {
$newObservers[] = $oldObserver;
}
}
$this->observers = $newObservers;
return $this;
}
public function notify() {
foreach($this->observers as $observer) {
$observer->run();
}
}
public function setstat($msg){
foreach($this->observers as $observer) {
$observer->setstat($msg);
}
}
}
עכשיו אנחנו מיישמים את האינטרפייס, את כל המחלקות של האינטרפייס (חובה).
מהמודל אבסטרקט הזה נוריש לכל המודלים במערכת שלנו.
כמו שאתם רואים, הכל פשוט – יש מערך של משקיפים, אפשר להוסיף נתונים, להוריד אותם, לשלוח אותם ולשמור מידע.
הפונקציות האלה מקבלות רק משקיפים. אין מה לגעת בפונקציות האלה יותר מדי, וכבר כל המודלים שיירשו מפה יהיו עם התכונות האלה.
ולבסוף, המודל שלנו.
Login.php
Class Login extends ModelAbstract{
function login($username, $password){}
function getLoginData(){
return array($username, $password, $ip);
}
}
function login($username, $password){}
function getLoginData(){
return array($username, $password, $ip);
}
}
זה מודל רגיל לכל דבר, מחלקה שבודקת ומטפלת בהתחברות ופונקציית getLoginData, שממנה המשקיף שלנו ייקח את המידע שהוא צריך.
בואו נחבר את הכול
אז תיישמו את המשקיף בקונטרולר שלכם או בכל קובץ שבו אתם יוצרים מופע של מחלקת Login (מודל Login).
$login = new Login();
$login->attach(new LoginObserver());
$login->setstat($login->getLoginData());
$login->notify();
$login->attach(new LoginObserver());
$login->setstat($login->getLoginData());
$login->notify();
וזהו.
פונקציית ה-run במשקיף המיוחד שלנו LoginObserver עבד.
יותר מזה, אפשר בקלות לצרף כמה משקיפים לכמה דיווחים או לדברים שונים שאתם רוצים לעשות עם המידע, פשוט עושים עוד attach.
והנה, יש לנו מנהל אירועים, משקיף, לוגר. תקראו לזה איך שאתם רוצים, לנהל אירועים בצורה פשוטה. (כן, אחרי שתיישמו את זה פעם אחת זה מאוד פשוט)
בצורה מקצועית, איכותית, בצורה ששומרת על הקוד גמיש, פתוח לשינויים וסגור לבאגים עתידיים עם שינוי פעולות המשקיף.
אפשר להשתמש בזה באין סוף דרכים וצורות שונות. כמובן שזה קוד הבסיס, וכל אחד יתאים את זה למערכת שלו. שינוי קל בקוד יידרש למי שמשתמש ב-Namespaces.
וזהו, מקווה שנהניתם.
תגובות לכתבה:
כתבה מעולה, מסבירה את עיקרון ה-observer בצורה יפה.
קצת פחות אהבתי את נוכחות ה-mvc במדריך כי כנראה שזה סתם מקשה על מי שלא סגור מה זה mvc.
http://phpguide.co.il/mvc_מפרידים_html_מ_php.htm
observerים מאוד נוכים במקרים שמחלקה אחת תלויה במידע של מחלקה אחרת וצריכה לדעת על עדכונים כאלה. למשל חנות יכולה "להירשם לעדכונים" של ספק וברגע שבספק משתנה כמות במלאי - הוא יעדכן את החנות.
השימוש הרחב של observer הוא גדול בתוכנות שממשיכות לרוץ לאורך זמן, ששם העדכון מידע הזה הכרחי יותר, מאשר באתר, שבו נראה עמוד עם מוצרי החנות עבור הרגע הנוכחי ואם אחר כך ישתנו הספקרים זה כבר לא יעניין אף אחד - כי התוכנה כבר סיימה לרוץ.
אחת השאלות הנפוצות ברעיונות עבודה היא לכתוב אובסרבר. שאלה שמראה בצורה מדהימה האם אתם יודעים משהו מעבר לכתיבת סקריפטים פרימיטיביים.
אחת הספריות הפופולאריות של javascript שמושטתת כל כולה על observerים היא knockout.js.
ספריה מדהימה שמאשרת ליצור מודלים ולחבר אותם לאלמנטים בעמוד. ברגע שמתעדכן משהו במודל - אוטומטית מתעדכן גם העמוד וההיפך.
למשל אם יש מודל "משתמש" ויש לו שדה "שם", יש בעמוד למעלה כיתוב "שלום %שם%" ולמטע שדה טקסט שמאשר לעדכן את אותו השם, הספריה תדאג באמצעות אובסרבינג לעדכן את ההודעה למעלה ואת המשתנה במודל ברגע שתזינו ערך חדש בשדה הטקסט וההיפך.
בנוסף, הייתי שמח לראות בצירוף למדריך גם trait של observer שיאפשר לחבר פונקציונאליות של observableלכל מחלקה. ממליץ לנסות לכתוב אחד כזה לצורך תרגול למי שבאמת רוצה ללמוד.
http://phpguide.co.il/Traits.htm
היי אלכס,
תודה על התגובה,
לפי אמונתי הדרך היחידה לעבוד עם זה כמו שצריך (!!) היא דרך הרכבה עם MVC,
אחרת יווצר באלגן,
בנוסף שכחתי לציין שיש Observer מובנה בPHP במחלקת SplSubject ו SplObserver
אבל זה תוסף שצריך להתקין בחלק מהמקרים.
תודה לך :)
יצא מדהים
מושקע יחסית למאמרים בעברית. יפה!
סתם שאלה,
לא עדיף לקחת את הקוד האחרון אחרון ולשים אותו ב-class של ה-login בתור ה__destruct
כי זה יעשה את אותה הפעולה, ואפילו לא נצטרך לרשום על כל פעולה את הקוד האחרון כי זה כבר יהיה מובנה.
כל מה שישאר לעשות זה להכין לו מה לעשות באירוע, ולקשר אותו באמצעות ה-extends?